package default_package;
import java.io.*;
import java.net.*;
import java.util.Date;
import java.util.Scanner;

public class MultiThreadHTTPServer implements Runnable{
	Socket connectionSocket;

public MultiThreadHTTPServer(Socket connectionSocket) {
	this.connectionSocket = connectionSocket;
}
/**
 * The main method of this class. Listens on a specified port and accepts sockets trying to connect.
 * Starts a new thread for each accepted socket.
 */
public static void main(String args[]) 
throws Exception {
   ServerSocket serverSocket = new ServerSocket(1234);
   // Waits for a socket to connect.
   System.out.println("Listening");
   while (true) {
	  // Accept the socket and start a new thread.
      Socket sock = serverSocket.accept();
      System.out.println("Connected");
      new Thread(new MultiThreadHTTPServer(sock)).start();
   }
}

public Socket getConnectionSocket() {
	return connectionSocket;
}

/**
 * This method runs the MultiThreadHTTPServer
 */
@Override
public void run() {
	try {
		boolean running = true;
		String HTTPVersion = null;
		// Setting up communications to read from and send to the client.
		BufferedReader inFromClient = new BufferedReader(new
					InputStreamReader (getConnectionSocket().getInputStream()));
		PrintWriter outToClient = new PrintWriter(getConnectionSocket().getOutputStream());		
		while(running) {
			// Reading a line (the command)
			String clientSentence = inFromClient.readLine();
			System.out.println(clientSentence);
			outToClient.print("Echo: " + clientSentence + "\n");
			outToClient.flush();
			// Parsing the command into an array
			String[] words = InputParser.parse(clientSentence);
			String command = words[0];
			// Check whether the request is valid
			if(! isValidRequest(words)) {
				outToClient.print("400 Bad request\r\n\r\n");
				outToClient.flush();
			}
			else {
			HTTPVersion = words[2];
			// Constructing the filepath.
			String filePath = InputParser.getFilePath(words[1], "127.0.0.1");
			// Executes the command
			boolean finished = execute(command, filePath, HTTPVersion, outToClient, inFromClient);
			running = ! (("HTTP/1.0").equals(HTTPVersion) || finished);
			running = false;
			}
			System.out.println("Received: " + clientSentence);
		}
		// Shutting down in- and output and closing connectionsocket.
		getConnectionSocket().shutdownInput();
		getConnectionSocket().shutdownOutput();
		getConnectionSocket().close();
		System.out.println("Connection closed");
	}
	catch (IOException e) {System.out.println(e);}
}

/**
 * This method checks whether the given array of strings contains a valid request
 * @param words	An array of strings containing a parsed command.
 * @return	Returns a boolean reflecting whether the request is valid.
 */
private boolean isValidRequest(String[] words) {
	if(words.length != 3)
		return false;
	if(!isValidCommand(words[0]))
		return false;
	
	return ("HTTP/1.0").equals(words[2]) || ("HTTP/1.1").equals(words[2]);
	}

	/**
	 * Method to check whether a given command is valid.
	 * @param command A string containing the command
	 * @return A boolean, true if the command is valid, else returns false
	 */
	private boolean isValidCommand(String command) {
		return (command.equals("HEAD") || command.equals("GET") || command.equals("PUT") || command.equals("POST"));
	}

	/**
	 * This method executes a certain command
	 * @param command	A string containing a command word
	 * @param filePath	A string with the filePath of the file on which the command needs to act
	 * @param HTTPVersion The HTTP version (HTTP/1.0 or HTTP/1.1)
	 * @param out		The PrintWriter used to write to the client
	 * @param inFromClient		The BufferedReader used to read the input from client
	 * @return Returns a boolean saying whether the connection should be closed
	 */
	private static boolean execute(String command, String filePath, String HTTPVersion, 
			PrintWriter out, BufferedReader inFromClient) {
		// Reading headers
		String headers = readBlock(inFromClient);
		String responseToClient = headers;
		// Execute the correct command
		if(command.equals("HEAD"))
			responseToClient = HEAD(filePath, HTTPVersion);
		else if(command.equals("GET"))
			responseToClient = GET(filePath, HTTPVersion, inFromClient);
		else if(command.equals("PUT"))
			responseToClient = PUT(filePath, readBlock(inFromClient), HTTPVersion);
		else if(command.equals("POST"))
			responseToClient = POST(filePath, readBlock(inFromClient), HTTPVersion);
		else
			responseToClient = (HTTPVersion + " 400 Bad request");
		// Print a response to the client.
		out.print(responseToClient + "\r\n");
		out.flush();
		//Return whether the connection should be closed.
		return headers.contains("Connection: close");
	}
	
	/**
	 * Method implementing the GET function
	 * @param uri The name or filePath of the file that needs to be retrieved
	 * @param HTTPVersion The HTTP version of the request (HTTP/1.0 or HTTP/1.1)
	 * @param inputStream A BufferedReader 
	 * @return
	 */
	private static String GET(String uri, String HTTPVersion, BufferedReader inputStream) {
		// Search for the file at the given location.
		File file = searchFile(uri);
		String response = "";
		if(file == null)
			// No file found.
			response = (HTTPVersion +" 404 Not found\n");
		else {
			Scanner scanner;
			try {
				// Add headers to response
				response += HEAD(uri, HTTPVersion);
				// Add content of file to response
				scanner = new Scanner(file);
				while(scanner.hasNext())
				response += scanner.nextLine();
			} catch (FileNotFoundException e) {	}
		}
		return response;
	}

	/**
	 * A method that returns headers of a file
	 * @param filePath	The filepath
	 * @param HTTPVersion The HTTP version of the request (HTTP/1.0 or HTTP/1.1)
	 * @return return Returns a string with the response for the client
	 */
	private static String HEAD(String filePath, String HTTPVersion) {
		String response = "";
		File file = searchFile(filePath);
		if(file == null) 
			return (HTTPVersion + " 404 Not Found\n");
		// add status line
		response += (HTTPVersion + " 200 OK\n");
		// add last modified
		response += ("Last modified : " + (new Date(file.lastModified())).toString() + "\n");
		// add content type
		response += ("Content type : " + "text/html\n");
		// add content length
		response += ("Content length : " + file.length() + "\n");		
		// Check if a date header should be returned.
		Date date;
		if(("HTTP/1.1").equals(HTTPVersion)) {
			date = new Date();
			response += date.toString();
		}
		// add blank line
		response += ("\n");
		return response;
	}

	/**
	 * A method that puts a given content at the specified filepath
	 * @param filePath	The filepath where the content should be put
	 * @param content	The content to be in the file
	 * @param HTTPVersion	The HTTP version of the request 1.0 or 1.1
	 * @return A string with the answer to the client
	 */
	private static String PUT(String filePath, String content, String HTTPVersion) {
		try {
			System.out.println(filePath);
			File temp = new File("src/html_files/" + filePath);
			String response;
			// Checks if the content is valid
			if(content == null || content.equals("\r\n")) {
				return (HTTPVersion + " 204 No content \n");
			}
			// Checks whether the file already exists, if not, creates a new file.
			if(searchFile(filePath) == null) {
				temp.createNewFile();
				response = (HTTPVersion + " 201 Created \n");
			}
			else {
				response = (HTTPVersion + " 200 OK \n");
			}
			// Return the headers
		    response += HEAD(filePath,HTTPVersion);
			FileWriter fw = new FileWriter(temp.getAbsoluteFile());
			BufferedWriter bw = new BufferedWriter(fw);
			// Write the content to file.
			bw.write(content);			
			System.out.println(temp.getAbsolutePath());
			bw.flush();
			bw.close();
			return response;
		}
		catch (IOException e) { System.out.println(e); return null;}
	}

	/**
	 * A method that posts a content in a file (adding it behind the existing content)
	 * @param filePath	The filepath of the file in which the content should be posted
	 * @param content	The content that should be posted
	 * @param HTTPVersion	The HTTP version of the request (1.0 or 1.1)
	 * @return
	 */
	private static String POST(String filePath, String content, String HTTPVersion) {
		File temp = new File("src/html_files/" + filePath);
		Scanner scanner;		
		String originalText = "";
		try {
			scanner = new Scanner(temp);
			// Reading file
			while(scanner.hasNext())
			originalText += scanner.nextLine();
			scanner.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		String modifiedText = originalText + "\r\n";
		// Adding the content to be posted to the file.
		modifiedText += content;
		// Putting the modified file in place.
		return PUT(filePath,modifiedText, HTTPVersion);
	}
	
	/**
	 * This method searches for a file with a given name
	 * @param name	The name of the file
	 * @return	Returns a file with the given name if it is found, else returns null
	 */
	private static File searchFile(String name) {
		File[] files = new File("src/html_files").listFiles();
		for(File file : files) {
			if(file.getName().equals(name))
				return file;
		}
		return null;
	}

	/**
	 * This method reads a block of text until it is ended with an empty line.
	 * @param inputStream	The inputstream to read
	 * @return	Returns a string, containing the block that has been read.
	 */
	private static String readBlock(BufferedReader inputStream) {
		String textBlock = "";
		String temp;
		try {
			boolean reading = true;
			while (reading) {
			temp = inputStream.readLine();
			System.out.println(temp);
			if(temp == null || temp.equals(""))
				reading = false;
			textBlock += (temp + "\r\n");
			}		
		}
		catch(IOException e) { System.out.print(e); }
		return textBlock;
	}
}